package listeners

import (
	"archive/tar"
	"blockchains-aio/conf"
	"blockchains-aio/http"
	"blockchains-aio/util"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"os"
	"path/filepath"
)

func RegisterContract(router *gin.Engine) {
	contract := router.Group("/contract")
	{
		contract.GET("/template", getAllTemplates)
		contract.POST("/template", uploadAnTemplate)
		contract.GET("/template/:templateName", getTemplate)

		//requirement.POST("/individual/:user/:proj", createIndividualProject)
		//requirement.GET("/individual", getAllTemplates)

		contract.POST("/individual/project", makeNewProject)
		contract.GET("/individual/project/:user", listProjects)
		contract.DELETE("/individual/project/:user", removeProject)
		contract.GET("/individual/project/:user/:proj/entry", getProjectEntries)
		contract.GET("/individual/project/:user/:proj/staticcheck/go", doGoProjectStaticcheck)
		contract.POST("/individual/project/:user/:proj/entry", writeFile)
		contract.POST("/individual/project/:user/:proj/entry/view", getFile)
		contract.DELETE("/individual/project/:user/:proj/entry", removeFile)

		contract.GET("/individual/project/:user/:proj/tar", downloadtProjectTar)
		//contract.POST("/individual/project/chaincode/:user/:proj", downloadCompiledProject)
	}

}

type ChainCodeInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Path    string `json:"path"`
	Lang    string `json:"lang"` // golang,node,java
}

//func downloadCompiledProject(ctx *gin.Context) {
//	user := ctx.Param("user")
//	proj := ctx.Param("proj")
//	ccInfo := new(ChainCodeInfo)
//
//	err := ctx.ShouldBind(ccInfo)
//	if err != nil {
//		ctx.JSON(400, NewErrorMessage(Chaincode, 2, "downloadCompiledProject", fmt.Sprintf("bad parameters: body")))
//		return
//	}
//
//	projectPath := fmt.Sprintf("%s/%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj, ccInfo.Path)
//	if !util.IsPathExists(projectPath) {
//		ctx.JSON(400, NewErrorMessage(Chaincode, 2, "downloadCompiledProject", fmt.Sprintf("bad parameters: proj or user")))
//		return
//	}
//
//	Type := int32(1)
//	if ccInfo.Lang == "golang" {
//
//	} else if ccInfo.Lang == "java" {
//		Type = int32(4)
//	} else if ccInfo.Lang == "node" {
//		Type = int32(2)
//	} else {
//		ctx.JSON(400, NewErrorMessage(Chaincode, 3, "downloadCompiledProject", fmt.Sprintf("bad parameters: body")))
//		return
//	}
//
//	label := ccInfo.Name + "_" + ccInfo.Version
//	compiledPackageName := label + "tar.gz"
//	desc := &lcpackager.Descriptor{
//		Path:  projectPath,
//		Type:  pb.ChaincodeSpec_Type(Type),
//		Label: label,
//	}
//	ccPkg, err := lcpackager.NewCCPackage(desc)
//	if err != nil {
//		ctx.JSON(400, NewErrorMessage(Chaincode, 3, "downloadCompiledProject", fmt.Sprintf("NewCCPackage err: %v", err)))
//		return
//	}
//
//	ctx.Header("Content-Type", "application/octet-stream")
//	ctx.Header("Content-Transfer-Encoding", "binary")
//	ctx.Header("Content-Disposition", "attachment; filename="+compiledPackageName)
//
//	log.Printf("serve file: %s", compiledPackageName)
//	//ctx.File(f.Name())
//	ctx.Writer.Write(ccPkg)
//}

func downloadtProjectTar(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	f, err := os.CreateTemp("", "blockchainsAio.*.tar")
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to create a temp file"))
		return
	}
	defer f.Close()
	//defer os.Remove(f.Name())

	if err := util.TarFolder(projectPath, tar.NewWriter(f)); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to tar project"))
		return
	}

	fStat, _ := f.Stat()
	ctx.Header("Content-Type", "application/octet-stream")
	ctx.Header("Content-Transfer-Encoding", "binary")
	ctx.Header("Content-Disposition", "attachment; filename="+fStat.Name())

	log.Printf("serve a file: %s", f.Name())
	ctx.File(f.Name())
}

func removeFile(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	entry := ctx.PostForm("entry")

	log.Printf("remove a file: entry:%s ", entry)

	filePath := fmt.Sprintf("%s/%s", projectPath, entry)
	if err := os.RemoveAll(filePath); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to remove the file: %v", err))
		return
	}

	ctx.JSON(200, "")

}

// curl -X DELETE localhost:39781/requirement/individual/project?user=abcdefg&proj=myproj
func removeProject(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Query("proj")

	if len(user) == 0 || len(proj) == 0 {
		ctx.JSON(400, fmt.Sprintf("user or proj parameter is null"))
		return
	}

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	if err := os.RemoveAll(projectPath); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to remove the project: %s", proj))
		return
	}

	ctx.JSON(200, "")
}

func getFile(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	entry := ctx.PostForm("entry")
	filePath := fmt.Sprintf("%s/%s", projectPath, entry)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found ", entry))
		return
	}

	log.Printf("read a file: entry:%s filename:%s", entry, filePath)
	contentBytes, err := os.ReadFile(filePath)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read content from entry:%s", entry))
		return
	}

	ctx.JSON(200, string(contentBytes))
}

func writeFile(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	entry := ctx.PostForm("entry")
	file, _ := ctx.FormFile("file")

	log.Printf("write a file: entry:%s filename:%s", entry, file.Filename)

	filePath := fmt.Sprintf("%s/%s", projectPath, entry)
	ctx.SaveUploadedFile(file, filePath)

	ctx.JSON(200, "")
}

func doGoProjectStaticcheck(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	//goStackCheckProg := os.Getenv("GO_STACKCHECK_PROG")
	//if len(goStackCheckProg) == 0 {
	//	ctx.JSON(400, fmt.Sprintf("GO_STACKCHECK_PROG variable is not set"))
	//	return
	//}

	dirs, err := util.Find(projectPath, "go.mod")
	if err != nil || len(dirs) == 0 {
		ctx.JSON(400, fmt.Sprintf("Not a golang project"))
		return
	}
	goModDir := filepath.Dir(dirs[0])

	log.Println("Path=", os.Getenv("PATH"))
	log.Println("goModDir=", goModDir)
	log.Println("GOPATH=", os.Getenv("GOPATH"))
	log.Println("FABRIC_CA_CLIENT_HOME=", os.Getenv("FABRIC_CA_CLIENT_HOME"))
	out, err := util.RunCmd("staticcheck", goModDir, "./...")
	if err != nil && out.Len() == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to run go static check: %v\n %s", err.Error(), out.String()))
		return
	}

	//	fake := `chaincode/smartcontract_test.go:17:6: type transactionContext is unused (U1000)
	//chaincode/smartcontract_test.go:22:6: type chaincodeStub is unused (U1000)
	//chaincode/smartcontract_test.go:27:6: type stateQueryIterator is unused (U1000)`
	ctx.JSON(200, out.String())
	//	ctx.JSON(200, fake)
	//
	//ctx.JSON(200, filesInfo)
}

func getProjectEntries(ctx *gin.Context) {
	user := ctx.Param("user")
	proj := ctx.Param("proj")

	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, proj)
	if !util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("%s not found for %s", proj, user))
		return
	}

	filesInfo, err := util.ListFiles(projectPath)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("list project structure error: %v", err))
		return
	}

	ctx.JSON(200, filesInfo)
}

func listProjects(ctx *gin.Context) {
	user := ctx.Param("user")
	if len(user) == 0 {
		ctx.JSON(400, fmt.Sprintf("the user is necessary"))
		return
	}

	userFolder := fmt.Sprintf("%s/%s", conf.GetContractRepIndividualsFolder(), user)
	if !util.IsPathExists(userFolder) {
		ctx.JSON(404, fmt.Sprintf("%s not found", user))
		return
	}

	entries, err := os.ReadDir(userFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read folder %s: %v ", userFolder, err))
		return
	}
	userProjects := []string{}
	//contractRepIndividualsFolderLength := len(conf.GetContractRepIndividualsFolder())
	for _, entry := range entries {
		if entry.IsDir() {
			//if ind := strings.Index(entry.Name(), conf.GetContractRepTemplatesFolder()); ind != -1 {
			//	name := entry.Name()[ind+contractRepIndividualsFolderLength:]
			//	userProjects = append(userProjects, name)
			//}
			userProjects = append(userProjects, entry.Name())
		}
	}

	ctx.JSON(200, userProjects)
}

func makeNewProject(ctx *gin.Context) {
	templateName := ctx.PostForm("templateName") //optional
	user := ctx.PostForm("user")                 //required
	project := ctx.PostForm("project")           //required

	//1. detect whether the project is existed,otherwise create one
	projectPath := fmt.Sprintf("%s/%s/%s", conf.GetContractRepIndividualsFolder(), user, project)
	if util.IsPathExists(projectPath) {
		ctx.JSON(400, fmt.Sprintf("The user(%s) has created the project before(%s)", user, project))
		return
	}

	if err := util.CreateAllDir(projectPath); err != nil {
		ctx.JSON(400, fmt.Sprintf("Can't create project %s,: %v", project, err))
		return
	}

	// 2. decompress template tar to user project if the template was chosen
	if len(templateName) != 0 {
		//mn, _, _ := strings.Cut(templateName, ".")
		templatesFolder := conf.GetContractRepTemplatesFolder()
		templateFilePath := fmt.Sprintf("%s/%s", templatesFolder, templateName)

		if !util.IsPathExists(templateFilePath) {
			ctx.JSON(400, fmt.Sprintf("file not found: %s", templateName))
			return
		}

		if err := util.UntarFile2(templateFilePath, projectPath); err != nil {
			ctx.JSON(400, fmt.Sprintf("Unrecognized error: %v", err))
			return
		}
	}

	// 3. list files' structure in the project
	filesInfo, err := util.ListFiles(projectPath)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("list project structure error: %v", err))
		return
	}

	ctx.JSON(200, filesInfo)

}

// curl -X POST http://localhost:39781/contract/individual/11/myproject -H "Content-Type: multipart/form-data" -F "upload[]=@./myproject/main.c" -F "upload[]=@./myproject/job.c" -F "upload[]=@./myproject/dep/dep:rs.lib"
//func createIndividualProject(ctx *gin.Context) {
//	form, _ := ctx.MultipartForm()
//	userId := ctx.Param("user")
//	projectName := ctx.Param("proj")
//	log.Println(userId, projectName)
//
//	files := form.File["upload[]"]
//
//	for _, file := range files {
//		log.Println(file.Filename)
//
//	}
//
//}

func getFileContentInTar(ctx *gin.Context, fileInTar string) {
	templateName := ctx.Param("templateName")
	templatesFolder := conf.GetContractRepTemplatesFolder()
	templateFilePath := fmt.Sprintf("%s/%s", templatesFolder, templateName)

	if !util.IsPathExists(templateFilePath) {
		ctx.JSON(400, fmt.Sprintf("file not found: %s", templateName))
		return
	}

	contentBytes, err := util.ReadFileInTar(templateFilePath, fileInTar)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read content from %s", templateName))
	}

	ctx.JSON(200, string(contentBytes))
}

func getTemplate(ctx *gin.Context) {
	templateName := ctx.Param("templateName")
	fileInTar := ctx.Query("fileInTar")
	templatesFolder := conf.GetContractRepTemplatesFolder()
	templateFilePath := fmt.Sprintf("%s/%s", templatesFolder, templateName)

	if !util.IsPathExists(templateFilePath) {
		ctx.JSON(400, fmt.Sprintf("file not found: %s", templateName))
		return
	}

	if len(fileInTar) == 0 {
		tarFilesInfo, err := util.ListTarFiles(templateFilePath)
		if err != nil {
			ctx.JSON(400, fmt.Sprintf("file not tar format: %v", err))
			return
		}

		ctx.JSON(200, tarFilesInfo)
	} else {
		getFileContentInTar(ctx, fileInTar)
	}

}

func uploadAnTemplate(ctx *gin.Context) {
	http.WorkThroughPostFormInitializationInGin(ctx)
	templatesFolder := conf.GetContractRepTemplatesFolder()

	file, _ := ctx.FormFile("file")
	fileName := file.Filename

	dst := fmt.Sprintf("%s/%s", templatesFolder, fileName)
	if err := ctx.SaveUploadedFile(file, dst); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to save template file: %v", err))
		return
	}

	ctx.JSON(200, fmt.Sprintf("'%s' uploaded!\n", file.Filename))
}

func getAllTemplates(ctx *gin.Context) {
	templatesFolder := conf.GetContractRepTemplatesFolder()
	log.Println("debug::", "templatesFolder=", templatesFolder)
	templates, err := util.GetAllFiles(templatesFolder, false)
	log.Println("debug::", "found templates,", fmt.Sprintf("%v", templatesFolder))
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("list error: %v", err))
		return
	}

	ctx.JSON(200, templates)
}
